<?php
namespace Websocket;
require_once __DIR__ . '/../Net/Connection.php';
require_once __DIR__ . '/../Net/Event.php';
require_once 'WebsocketBuffer.php';
use Net\Connection;
use Net\Event;

class WebsocketConnection extends Connection {
    public $handshake = false;
    public $handshakeheader = [];
    
    /**
     * @var WebsocketBuffer
     */
    protected $_buffer;
    
    /**
     * @return WebsocketBuffer
     */
    public function getBuffer()
    {
        return $this->_buffer;
    }
    
    protected function _init()
    {
        parent::_init();
        $this->_buffer = new WebsocketBuffer();
    }
    
    /**
     * 发送websocket消息
     * @param $message
     * @return $this
     */
    public function sendMessage($message) {
        $packages = self::packWebsocketProtocol($message);
    
        if(is_array($packages)) {
            foreach($packages as $package) {
                $this->send($package);
            }
        } else {
            $this->send($packages);
        }
        
        return $this;
    }
    
    public function close() {
        if($this->handshake) {
            $this->send(self::packWebsocketProtocol('', 1, 8));
        }
        
        parent::close();
    }
    
    public function onMessage($callback) {
        return $this->on('message', $callback);
    }
    
    public function triggerOnMessage($params) {
        return $this->triggerOn('message', $params);
    }
    
    public function encodeKey($key) {
        return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
    }
    
    /**
     * 解析http协议
     * @param $content
     * @return array
     */
    static public function parseHttpProtocol($content) {
        list($header, $body) = explode("\r\n\r\n", $content, 2);
    
        $lines = explode("\n", $header);
        $header = [];
    
        foreach ($lines as $line) {
            $line = trim($line);
        
            if(empty($line)) {
                continue;
            }
        
            list($key, $value) = explode(':', $line, 2);
            $header[strtolower(trim($key))] = trim($value);
        }
        
        return [$header, urldecode($body)];
    }
    
    /**
     * 打包websocket数据包
     * @param $message
     * @param int $fin
     * @param int $opcode
     * @param int $mask
     * @return array|string
     */
    static function packWebsocketProtocol($message, $fin=1, $opcode=1, $mask=0) {
        //最大发送长度, 按照32位最大整数分割进行分割
        $MAX_LENGTH = 0x7FFFFFFF;
        $package = chr(($fin ? 0x80 : 0) + $opcode);
        $msgLength = strlen($message);
        $maskKey = chr(mt_rand(1,125)) . chr(mt_rand(1,125)) . chr(mt_rand(1,125)) . chr(mt_rand(1,125));
        $char2 = $mask ? 0x80 : 0;
    
        if($msgLength < 126) {
            $package .= chr($char2 + $msgLength);
        } else if($msgLength <= 0xFFFF) {
            $package .= chr($char2 + 126);
            $package .= chr(($msgLength >> 8) & 0xFF);
            $package .= chr($msgLength & 0xFF);
        } else if($msgLength <= $MAX_LENGTH) {
            $package .= chr($char2 + 127);
            
            $xlen = base_convert($msgLength, 10, 16);
            $xlen = str_pad($xlen, 16, '0', STR_PAD_LEFT);
            $lenAry = str_split($xlen, 2);
            
            foreach($lenAry as $hex) {
                $package .= chr(intval(base_convert($hex, 16, 10)));
            }
            
        } else {
            //数据太长, 分包发送
            $packAry = [];
            $splitAry = str_split($message, $MAX_LENGTH);
            $count = count($splitAry);
            
            for($i=0; $i<$count; $i++) {
                $packAry []= self::packWebsocketProtocol($splitAry[$i], $i == $count-1, $opcode, $mask);
            }
            
            return $packAry;
        }
    
        if($mask) {
            $package .= $maskKey;
        }
    
        if($mask) {
            for($i=0; $i<$msgLength; $i++) {
                $message[$i] = chr(ord($message[$i]) ^ ord($maskKey[$i%4]));
            }
        }
    
        $package .= $message;
        return $package;
    }
}
